﻿using System.Linq;
using System.Reflection;
using System.Text.Json.Nodes;
using System.Text.RegularExpressions;
using System.Xml.XPath;
using Freesql.Tool.Models;
using FreeSql.DataAnnotations;
using RestSharp;
using static SKIT.FlurlHttpClient.Wechat.TenpayV3.Models.CreateApplyForSubMerchantApplymentRequest.Types.Business.Types.SaleScene.Types;

namespace Freesql.Tool.Helpers
{
    public static class ReflectionHelper
    {
        private static string AssemblyXmlName => DomainAssembly.GetName().Name! + ".xml";

        private static Assembly DomainAssembly => typeof(LF.Domain.DomainModule).Assembly;

        public static string ToLowerFirst(string str)
        {
            if (str.Length < 2)
                return str.ToLower();
            return str.Substring(0, 1).ToLower() + str.Substring(1);
        }

        public static string ToUpperFirst(string str)
        {
            if (str.Length < 2)
                return str.ToUpper();
            return str.Substring(0, 1).ToUpper() + str.Substring(1);
        }

        public static bool IsNavgation(this PropertyInfo info)
        {
            var attr = info.GetCustomAttribute<NavigateAttribute>(true);
            return attr != null;
        }
        /// <summary>
        /// 获取属性列表
        /// </summary>
        /// <param name="assembly"></param>
        /// <param name="classPath"></param>
        /// <param name="isEnum"></param>
        /// <returns></returns>
        public static List<Property> GetProperties(string classPath,bool vue,out bool isEnum)
        {
            isEnum = false;
            var list = new List<Property>();
            string[] cloums = vue? new string[] { "CreatTime", "UpdateTime", "IsDelete" } : new string[] { "IsDelete" };//"Id"

            var doc = new XPathDocument(AssemblyXmlName);
            var nav = doc.CreateNavigator();
            var type = DomainAssembly.GetType(classPath);
            if (type != null)
            {
                foreach (var item in type.GetProperties())
                {
                    if (cloums.Any(p => p == item.Name) || item.PropertyType.BaseType?.Name== "BaseEntity")
                    {
                        continue;
                    }
                    if (item.IsNavgation())
                    {
                        continue;
                    }
                    var property = new Property { Name = item.Name, Component = "Input", Required = "true", IsEnum = false };
                    if (item.Name == "Id")
                    {
                        property.Type = "Guid?";
                        property.Required = "false";
                    }
                    else if (item.PropertyType == typeof(bool))
                    {
                        property.Type = "bool";
                    }
                    else if (item.PropertyType == typeof(Guid))
                    {
                        property.Type = "Guid";
                    }
                    else if (item.PropertyType == typeof(Nullable<Guid>))
                    {
                        property.Type = "Guid?";
                        property.Required = "false";
                    }
                    else if (item.PropertyType == typeof(long))
                    {
                        property.Type = "long";
                        property.Component = "InputNumber";
                    }
                    else if (item.PropertyType == typeof(Nullable<long>))
                    {
                        property.Type = "long?";
                        property.Component = "InputNumber";
                        property.Required = "false";
                    }
                    else if (item.PropertyType == typeof(Nullable<bool>))
                    {
                        property.Type = "bool?";
                        property.Required = "false";
                    }
                    else if (item.PropertyType == typeof(int))
                    {
                        property.Type = "int";
                        property.Component = "InputNumber";
                    }
                    else if (item.PropertyType == typeof(Nullable<int>))
                    {
                        property.Type = "int?";
                        property.Component = "InputNumber";
                        property.Required = "false";
                    }
                    else if (item.PropertyType.IsEnum)
                    {
                        isEnum = true;
                        property.Type = item.Name;
                        property.IsEnum = true;
                        property.Component = "RadioGroup";
                        Array enumItems = Enum.GetValues(item.PropertyType);
                        var infos = new List<EnumInfo>();
                        foreach (var enumItem in enumItems)
                        {
                            var enumName = $"{item.Name}.{enumItem}";
                            infos.Add(new EnumInfo { Name = enumName, Desc = GetEnumDesc(enumName) ?? enumItem.ToString()! });
                        }
                        property.EnumInfos = infos;

                    }
                    else if (item.PropertyType == typeof(string))
                    {
                        property.Type = "string";
                    }
                    else if (item.PropertyType == typeof(decimal))
                    {
                        property.Type = "decimal";
                        property.Component = "InputNumber";
                    }
                    else if (item.PropertyType == typeof(Nullable<decimal>))
                    {
                        property.Type = "decimal?";
                        property.Required = "false";
                        property.Component = "InputNumber";
                    }
                    else if (item.PropertyType == typeof(DateTime))
                    {
                        property.Type = "DateTime";
                        property.Component = "DatePicker";
                    }
                    else if (item.PropertyType == typeof(Nullable<DateTime>))
                    {
                        property.Type = "DateTime?";
                        property.Required = "false";
                        property.Component = "DatePicker";
                    }
                    else
                    {
                        property.Type = "string";
                    }
                    var xpath = $"/doc/members/member[@name='P:{classPath}.{item.Name}']/summary";
                    var description = nav.SelectSingleNode(xpath)?.Value?.Trim();
                    property.Description = string.IsNullOrEmpty(description) ? item.Name : description;
                    list.Add(property);
                }
            }
            return list;
        }

        private static string? GetEnumDesc(string classPath)
        {
            var path = Path.Combine(AppContext.BaseDirectory, typeof(LF.Common.AppConfig).Assembly.GetName().Name + ".xml");
            var doc = new XPathDocument(path);
            var nav = doc.CreateNavigator();
            var xpath = $"/doc/members/member[@name='F:LF.Common.{classPath}']/summary";
            var description = nav.SelectSingleNode(xpath)?.Value?.Trim();
            return description;
        }

        public static string GetDescription(string classPath)
        {
            var path = Path.Combine(AppContext.BaseDirectory, AssemblyXmlName);
            var doc = new XPathDocument(path);
            var nav = doc.CreateNavigator();
            var xpath = $"/doc/members/member[@name='T:{classPath}']/summary";
            var description = nav.SelectSingleNode(xpath)?.Value?.Trim();
            return description ?? "";
        }

        public static List<Enums> GetEnums()
        {
            var list = new List<Enums>();
            var type = typeof(LF.Common.Gender).Assembly;
            var types = type.GetTypes();
            foreach(var item in types)
            {
                if (item.IsEnum)
                {
                    var path = Path.Combine(AppContext.BaseDirectory, typeof(LF.Common.AppConfig).Assembly.GetName().Name + ".xml");
                    var doc = new XPathDocument(path);
                    var nav = doc.CreateNavigator();
                    var xpath = $"/doc/members/member[@name='T:{item.Namespace}.{item.Name}']/summary";
                    var description = nav.SelectSingleNode(xpath)?.Value?.Trim();
                   
                    Array enumItems = Enum.GetValues(item);
                    var infos = new List<EnumInfo>();
                    foreach (var enumItem in enumItems)
                    {
                        var enumName = $"{item.Name}.{enumItem}";
                        var value = (int)enumItem;
                        infos.Add(new EnumInfo { Name = enumItem.ToString()!, Desc = GetEnumDesc(enumName) ?? enumItem.ToString()!, Value = value });
                    }

                    list.Add(new Enums { Name = item.Name, Desc = description ?? item.Name, EnumInfos = infos });
                }
            }
            return list.OrderBy(p => p.Name).ToList();
        }

        private static string GetMethodType(MemberInfo type)
        {
            var attrs =  type.GetCustomAttributes();
            if (attrs?.Count() > 0)
            {
                foreach(var item in attrs)
                {
                    var cust = item.GetType();
                    if (cust.Name.StartsWith("Http"))
                    {
                        return cust.Name.Replace("Attribute","").Replace("Http","").ToLower();
                    }
                }
            }
            return type.Name.StartsWith("Get") ? "get" : "post";
        }
        #region api
        static List<string> ActionNmaes = new List<string>();
        public static List<Api> GetApi(Type type)
        {
            var list = new List<Api>();
            var assembly = type.Assembly;
            var path = Path.Combine(AppContext.BaseDirectory, assembly.GetName().Name + ".xml");
            var doc = new XPathDocument(path);
            var nav = doc.CreateNavigator();
           
            if (type == null)
            {
                return new List<Api>();
            }
            var name = type.Name;
            var methods = type!.GetMethods();
            foreach(var item in methods)
            {
                if (item.IsPublic && item.Module.Name.StartsWith("LF.Application"))
                {
                    var paras = item.GetParameters();
                    var returnType = GetArgType(item.ReturnType);
                    var inter = type.GetInterface($"I{type.Name}");
                    var xAttr = $"{inter!.FullName}.{item.ToString()!.Split(" ")[1]}";
                    var xpath = $"/doc/members/member[@name='M:{xAttr}']/summary";
                    var description = nav.SelectSingleNode(xpath)?.Value?.Trim();

                    var actionName = GetActionName(item);
                    if (ActionNmaes.Contains(actionName))
                    {
                        actionName = ToLowerFirst(item.Name);
                    }
                    else
                    {
                        ActionNmaes.Add(actionName);
                    }

                    var api = new Api { Path = $"{name.ToLower().Replace("service","")}/{GetActionName(item, true)}", Out = returnType, Type = GetMethodType(item), Desc = description ?? item.Name, Name = actionName };
                    api.ArgName = "input";
                    api.ArgType = "any";
                    if(paras?.Length == 0)
                    {
                        api.ArgName = "";
                        api.ArgType = "";
                    }
                    else if (paras?.Length == 1)
                    {
                        var pType = paras[0].ParameterType;
                        string pName;
                        if (pType.Namespace!.StartsWith("LF."))
                        {
                            pName = pType.Name;
                        }
                        else
                        {
                            pName = GetArgType(pType);
                        }
                        api.ArgType = pName;
                    }
                    list.Add(api);
                }
            }
            ActionNmaes.Clear();
            return list;
        }

        public static List<PropertyApi> GetDto(Type type)
        {
            var list = new List<PropertyApi>();
            var properties = type.GetProperties();
            foreach(var item in properties)
            {
                list.Add(new PropertyApi { Name = item.PropertyType.IsNullableType() ? item.Name.ToFirstLow() + "?" : item.Name.ToFirstLow(), Type = GetArgType(item.PropertyType), IsEmun = item.PropertyType.IsEnum });
            }
            return list;
        }
      

        private static string GetActionName(MethodInfo method,bool split =false)
        {
            string name;
            var attrs = method.GetCustomAttributes();
            var any = false;
            if (attrs?.Count() > 0)
            {
               any = attrs.Any(p => p.GetType().Name.StartsWith("Http"));
            }
            if (!any)
            {
                var reg = new Regex(@"Get|Post");
                name = reg.Replace(method.Name, "");
                if (string.IsNullOrWhiteSpace(name))
                {
                    name = method.Name;
                }
            }
            else
            {
                name = method.Name;
            }
            
            if (split)
            {
                name = string.Concat(name.Select((p, i) => i > 0 && char.IsUpper(p) ? "-" + p : p.ToString()));
            }
            name = ToLowerFirst(name);
            var keys = new string[] { "delete" };
            if (keys.Contains(name))
            {
                name = "my"+ string.Concat(name.Select((p, i) => i == 0 && char.IsLower(p) ? p.ToString().ToUpper() : p.ToString())); ;
            }
            name = name.Replace("Async", "");
            return ToLowerFirst(name);
        }

        #endregion

        static Dictionary<Type, string> Type2Ts = new Dictionary<Type, string>
        {
            { typeof(bool),"boolean"},
            { typeof(Nullable<bool>),"boolean|null"},
            { typeof(Guid),"string"},
            { typeof(Nullable<Guid>),"string|null"},
            { typeof(long),"number"},
            { typeof(Nullable<long>),"number|null"},
            { typeof(int),"number"},
            { typeof(Nullable<int>),"number|null"},
            { typeof(decimal),"number"},
            { typeof(Nullable<decimal>),"number|null"},
            { typeof(float),"number"},
            { typeof(Nullable<float>),"number|null"},
            { typeof(string),"string"},
            { typeof(DateTime),"string"},
            { typeof(Nullable<DateTime>),"string|null"},
            { typeof(DateOnly),"string"},
            { typeof(Nullable<DateOnly>),"string|null"},

        };
        private static string GetArgType( Type type)
        {
            string argType;
            if (Type2Ts.ContainsKey(type))
            {
                argType = Type2Ts[type];
            }
            else
            {
                if (type.IsGenericType)
                {
                    
                    if (type.IsArrayOrList())
                    {
                        if (type.GenericTypeArguments.Count() > 0)
                            argType = $"Array<{GetArgType(type.GenericTypeArguments[0])}>";
                        else
                            argType = $"Array<any>";
                    }
                    else if(type.FullName!.StartsWith(typeof(Task<>).FullName!))
                    {
                        argType = GetArgType(type.GenericTypeArguments[0]);
                    }
                    else
                    {
                        var reg = new Regex(@"`[0-9]+");
                        var typeName = reg.Replace(type.Name, "");
                        if (type.GenericTypeArguments.Count() > 0)
                            argType = $"{typeName}<{GetArgType(type.GenericTypeArguments[0])}>";
                        else
                            argType = $"{typeName}<any>";
                    }
                    
                }
                else if(type.IsArray)
                {
                    var arryType = type.Assembly.GetType(type.FullName!.Replace("[]",""));
                    if (arryType != null)
                    {
                        argType = $"Array<{GetArgType(arryType)}>";
                    }
                    else
                    {
                        argType = $"Array<any>";
                    }
                    
                }
                else
                    argType = type.Name;
            }
            if (argType == "Result")
            {
                argType = "Result<string>";
            }
            else if (argType.IndexOf("FileContentResult")!=-1)
            {
                argType = argType.Replace("FileContentResult", "Blob");
            }
            return argType;
        }

        static Dictionary<string, string> Api2Ts = new Dictionary<string, string>
        {
            { "integer","number" },
            { "array","Array"}
        };

        private static string ToTsType(string type)
        {
            if (Api2Ts.ContainsKey(type))
            {
                return Api2Ts[type];
            }
            var reg = new Regex(@"(?<=Result`1\[\[).*?(?=,)");
            if (reg.IsMatch(type))
            {
                type = $"Result<{reg.Match(type).Value.Split('.').Last()}>";
            }
            else
            {
                type = type.Split('.').Last();
            }
            return type;
        }

        public static List<Api> GetApiFromSwagger(string url)
        {
            var list = new List<Api>();
            using (var client = new RestClient(url))
            {
                var reponse = client.Execute(new RestRequest());
                JsonNode? json = JsonObject.Parse(reponse?.Content ?? "");
                var paths = json!["paths"] as JsonObject;

                foreach(var node in paths!)
                {
                    var path = node.Key;
                    var value = node.Value as JsonObject ;
                    foreach (var vv in value!)
                    {
                        var api = new Api { Path = path };
                        var method = vv.Key;
                        api.Type = method;
                        api.Desc = vv.Value?["summary"]?.GetValue<string>() ?? "";
                        var paras = vv.Value!["parameters"] as JsonArray;
                        if (paras != null)
                        {
                            var inputs = new List<string>();
                            foreach (var item in paras!)
                            {
                                var pName = item!["name"]!.GetValue<string>();
                                var pType = item!["schema"]!["type"] ?? item!["schema"]!["$ref"];
                                if (Api2Ts.ContainsKey(pType!.GetValue<string>()))
                                {
                                    pType = Api2Ts[pType!.GetValue<string>()];
                                }
                                inputs.Add($"{ToLowerFirst(pName)}:{pType}");
                            }
                            api.ArgName = string.Join(" ,", inputs);
                        }
                        var req = vv.Value!["requestBody"];
                        if (req != null)
                        {
                           
                            var aType = req["content"]!["application/json"]!["schema"]!["type"] ?? req["content"]!["application/json"]!["schema"]!["$ref"];
                            if (aType != null)
                            {
                                api.ArgType = ToTsType(aType!.GetValue<string>());
                                api.ArgName = "input";
                                var items = req["content"]!["application/json"]!["schema"]!["items"];
                                if (items != null)
                                {
                                    api.ArgType += $"<{ToTsType((items!["type"]?? items!["$ref"])!.GetValue<string>())}>";
                                }
                            }
                        }

                        var res = vv.Value!["responses"];
                        if (res != null)
                        {
                            if (res!["200"] != null)
                            {
                                var schema = res!["200"]!["content"]!["application/json"]!["schema"];

                                var type = schema!["$ref"] ?? schema!["type"];
                                api.Out = ToTsType(type!.GetValue<string>());
                                var items = schema["items"];
                                if (items != null)
                                {
                                    var key = items["$ref"] ?? items["type"];
                                    var oType = ToTsType(key!.GetValue<string>());
                                    api.Out += $"<{oType}>";
                                }
                            }
                            else
                            {
                                api.Out = "any";    
                            }
                          
                        }
                        var apiNames = new Regex(@"^/|/$").Replace(new Regex(@"\{.*?\}").Replace(api.Path,"").Replace("//","/"),"").Split("/");
                        if (apiNames.Length < 4)
                        {
                            api.File = ToTuofeng(apiNames.Last());
                            api.Name = api.Type;
                        }
                        else
                        {
                            api.File = ToTuofeng(apiNames[2]);
                            api.Name = apiNames.Last();
                        }
                        api.Name = ToTuofeng(api.Name);
                        var keyWords = new string[] { "delete" };
                        if (keyWords.Contains(api.Name))
                        {
                            api.Name = "my" + ToUpperFirst(api.Name);
                        }

                        if(string.IsNullOrWhiteSpace(api.Desc))
                        {
                            api.Desc = api.Name;
                        }

                        var reg = new Regex(@"\{.*?\}");
                        if (reg.IsMatch(api.Path))
                        {
                            foreach(Match item in reg.Matches(api.Path))
                            {
                                api.Path = api.Path.Replace(item.Value, @"$" + item.Value);
                            }
                        }

                        if (api.Out == "Result")
                        {
                            api.Out = "Result<string>";
                        }
                        list.Add(api);

                    }
                }
            }
            return list;
        }

        private static string ToTuofeng(string str)
        {
            if (str.IndexOf("-") != -1)
            {

                var temp = -1;
                var names = str.Select((x, i) => {
                    if (x == '-')
                    {
                        temp = i + 1;
                        return char.MinValue;
                    }
                    if (i == temp)
                    {
                        temp = -1;
                        return char.Parse(str[i].ToString().ToUpper());
                    }
                    return x;

                });
                str = string.Join("", names.Where(p => p != char.MinValue));

            }
            return str;

        }

    }
}
